/*******************************************************************************
 * @file Copyright 2009 The CREATE Lab at CMU
 * 
 *       This file is part of JubJub, an open source project.
 *       http://code.google.com/p/jubjub/
 * 
 *       Redistribution and use in source and binary forms, with or without
 *       modification, are permitted provided that the following conditions are
 *       met:
 * 
 *       Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 * 
 *       Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 * 
 *       Neither the name of Carnegie Mellon nor the names of its contributors
 *       may be used to endorse or promote products derived from this software
 *       without specific prior written permission.
 * 
 *       THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *       "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *       LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 *       PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 *       HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *       SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *       LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *       DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *       THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *       (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 *       OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *******************************************************************************/

package edu.cmu.ri.createlab.jj.blocks;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import edu.cmu.ri.createlab.jj.JubJub.EmptyStringException;
import edu.cmu.ri.createlab.jj.blocks.BlockDefs.*;
import edu.cmu.ri.createlab.jj.projects.JJClass;
import edu.cmu.ri.createlab.jj.visitors.*;

/**
 * 
 * @author Erik Pasternak
 * 
 * ExpressionBlock is a way of creating simple expression components in a language.
 * These are similar to many of the Numbers blocks in Scratch in that they allow
 * two elements and an operation. There are three basic types of expressions,
 * Logical ('<', '==', etc.), Arithmetic ('+','*','%', etc), and Assignment
 * ('='). ExpressionBlock blocks always take two arguments. Since the types of
 * arguments allowed vary by language it is left up to the interface to ensure
 * only valid arguments are used. The first round should allow only basic var
 * types except in assignment, for which the second argument may be another
 * ExpressionBlock.
 *
 */
public class ExpressionBlock implements BlockInterface
{
   /**
    * \defgroup BlockVars These variables are common to all BlockInterface types.
    */
   /*@{*/

   ///The name given to this block. CodeBlock and BlockBlock types should have unique names
   private String name;
   ///The JJClass which these blocks belong to and where they will be written to
   ///when output as code/xml
   private JJClass classOwner;
   ///What type of block this is from the list of BlockTypes
   private BlockTypes type;
   ///A unique ID for this block
   private int id;
   ///A list of RefBlock instances that reference this block.
   private List<RefBlock> refs;
   ///Whether this block is still valid or not
   private boolean valid;
   ///The comment for this block
   private String comment;
   
   /*@}*/
   
   /**
    * \defgroup ExpressionVars These variables are only used by ExpressionBlock.
    */
   /*@{*/
   
   ///The generic type of expression, either Assignment, Arithmetic, or Logical.
   ExpressTypes eType;
   ///The symbol used in this expression, such as '=', '/', '<='
   String symbol;
   ///The first input to the expression
   RefBlock in1;
   ///The second input to the expression
   RefBlock in2;
   
   /*@}*/
   
   /**
    * 
    * @param owner Which JJClass it belongs to
    * @param ID A globally unique identifier
    * @param eType The type of expression (Logical, Arithmetic, or Assignment)
    * @param symbol The string symbol that will be used in this expression
    * @throws EmptyStringException
    * 
    * Creates a new expression block with the necessary parameters. Once an ExpBlock has been
    * created its eType cannot be changed. This is because the eType determines how it can
    * be used and would be difficult to update if the eType could change. Also, the name is
    * maintained internally as a string representation of its code output.
    * 
    * How many eType updates would the eBlock update if the eBlock update could eType update?
    */
   public ExpressionBlock(JJClass owner, int ID, ExpressTypes eType, String symbol)
   throws Exception
   {
      this.valid = true;
      this.classOwner = owner;
      this.id = ID;
      this.type = BlockTypes.EXPRESSION;
      this.eType = eType;
      this.refs = new LinkedList<RefBlock>();
      this.symbol = symbol;
      this.in1 = null;
      this.in2 = null;
      this.comment = "";
      updateName();
   }
   
   public ExpressionBlock clone(int ID) throws Exception
   {
      ExpressionBlock tempBlock = new ExpressionBlock(classOwner, ID, eType, symbol);
      return tempBlock;
   }

   /**
    * \defgroup BlockMethods These methods are common to all blocks.
    */
   /*@{*/
   
   ///Sets the comment for this block
   public void setComment(String comment)
   {
      this.comment = comment;
   }
   ///Gets the comment for this block
   public String getComment()
   {
      return comment;
   }
   
   ///Return whether this block is still valid or if it is deleted pending
   ///removal of all its refs.
   public boolean isValid()
   {
      return valid;
   }
   
   ///Sets this block to not Valid and updates all its refs
   public void deleteBlock()
   {
      valid = false;
      this.updateRefs();
   }
   
   ///Sets this block to Valid and updates all its refs
   public void undeleteBlock()
   {
      valid = true;
      this.updateRefs();
   }
   
   ///Accepts a BlockVisitorInterface and visits itself
   public BVReturn accept(BlockVisitorInterface bv)
   {
      bv.visit(this);
      return null;
   }
   
   ///returns the JJClass that owns this block
   
   public JJClass getOwner()
   {
      return classOwner;
   }

   ///returns the id for this block
   
   public int getID()
   {
      return id;
   }

   ///returns the name of this block
   
   public String getName()
   {
      return name;
   }

   ///returns the BlockTypes type of this block
   
   public BlockTypes getType()
   {
      return type;
   }

   ///ExpressionBlock doesn't own any references so it never has an error
   ///The RefBlock referencing this block might have an error, but that
   ///doesn't matter to the ExpressionBlock.
   public boolean hasError()
   {
      return false;
   }
   
   ///Since it owns no refs it doesn't have anything to check for errors
   public void checkError()
   {
      return;
   }
   
   ///This makes it easier for the garbage collector to remove this block
   ///by ensuring all the references it contains are removed.
   public void destroyBlock()
   {      
      if(this.in1 != null) this.in1.remove();
      this.in1 = null;
      if(this.in2 != null) this.in2.remove();
      this.in2 = null;
      
      Iterator<RefBlock> it = this.refs.iterator();
      while(it.hasNext())
      {
         it.next().clearBlock();
      }
      this.refs.clear();
   }

   ///sets the JJClass that owns this block
   
   public void setOwner(JJClass owner)
   {
      this.classOwner = owner;
      
   }

   ///sets the name of this block as well as its callName.
   
   public void setName(String name) throws EmptyStringException
   {
      ///If the name is of 0 length we've made a mistake and need to throw
      ///an exception.
      if(name.length() == 0) 
      {
         throw new EmptyStringException("Empty string name");
      }
      this.name = name;
   }
   
   ///Adds a RefBlock to the list of references
   
   public void addRef(RefBlock ref)
   {
      if(refs.contains(ref)) return;
      refs.add(ref);
   }

   ///Removes the given RefBlock from the list of references
   
   public void remRef(RefBlock ref)
   {
      refs.remove(ref);
   }
   
   ///Returns a list of RefBlock types that reference this block
   public List<RefBlock> getRefs()
   {
      return refs;
   }

   ///Updates all the RefBlock references to this block
   
   public void updateRefs()
   {
      Iterator<RefBlock> it = refs.iterator();
      while(it.hasNext())
      {
         it.next().update();
      }
   }
   
   /*@}*/
   
   /**
    * \defgroup ExpressionMethods These methods are only used by ExpressionBlock.
    */
   /*@{*/
   
   ///Returns the type of expression block. The type can only be set at creation
   ///and determines what symbols are available to this block.
   public ExpressTypes getEType()
   {
      return eType;
   }
   
   ///Returns the symbol used in this expression
   public String getSymbol()
   {
      return symbol;
   }
   
   ///Returns the first input to the expression
   public RefBlock getFirstIn()
   {
      return in1;
   }
   ///Returns the second input to the expression
   public RefBlock getSecondIn()
   {
      return in2;
   }
   
   ///Returns the symbol used in this expression
   public void setSymbol(String symbol)
   {
      this.symbol = symbol;
   }
   
   ///Returns the first input to the expression
   public void setFirstIn(RefBlock ref)
   {
      this.in1 = ref;
      if(ref != null) ref.setOwner(this);
      updateName();
   }
   ///Returns the second input to the expression
   public void setSecondIn(RefBlock ref)
   {
      this.in2 = ref;
      if(ref != null) ref.setOwner(this);
      updateName();
   }
   
   ///Sets the name of the block to the same as its contents
   ///imply it would be typed in code.
   public void updateName()
   {
      name = "";
      if(in1 != null)
         name += in1.getName();
      name += symbol;
      if(in2 != null)
         name += in2.getName();
   }
   
   /*@}*/
   
   ///Returns a Java safe swapname
   public String toSwapName()
   {
      String s = "";
      RefBlock ref = this.getFirstIn();
      if(ref == null || ref.getBlock() == null) return s;
      switch(ref.getBlock().getType())
      {
      case VAR:
      {
         VarBlock tempVar = (VarBlock)ref.getBlock();
         if(tempVar.isLiteral()) s += tempVar.getValue(0);
         else s += tempVar.getCallName();
         break;
      }
      case EXPRESSION:
         ExpressionBlock tempExp = (ExpressionBlock)ref.getBlock();
         s += tempExp.toSwapName();
         break;
      default:
         System.err.println("Error:Expression can only contain vars and expressions");
         break;
      }
      
      s += this.symbol;
      
      ref = this.getSecondIn();
      switch(ref.getBlock().getType())
      {
      case VAR:
      {
         VarBlock tempVar = (VarBlock)ref.getBlock();
         if(tempVar.isLiteral()) s += tempVar.getValue(0);
         else s += tempVar.getCallName();
         break;
      }
      case EXPRESSION:
         ExpressionBlock tempExp = (ExpressionBlock)ref.getBlock();
         s += tempExp.toSwapName();
         break;
      default:
         System.err.println("Error:Expression can only contain vars and expressions");
         break;
      }
      
        
      return s;
   }
   
   ///Returns a readable String representation of the block
   public String toString()
   {
      String s = "";
      s += "Type:" + type;
      s += " ID:" + id;
      s += " Name:" + name;
      s += " Owner:" + classOwner;
      s += " ExpType:" + eType;
      s += " Symbol:" + symbol;
      s += " In1:" + in1.getName();
      s += " In2:" + in2.getName();
      s += " NumRefs:" + refs.size();
      s += " AsCode:\"" + in1.getName() + " " + symbol + " " + in2.getName() + "\"";
      
      return s;
   }
   
   public static void main(String[] args)
   {
      VarBlock vb1;
      VarBlock vb2;
      ExpressionBlock eb1, eb2, eb3;
      try
      {
         vb1 = new VarBlock("VB1", null, 1, VarTypes.INT);
         vb2 = new VarBlock("VB2", null, 2, VarTypes.BOOLEAN, null, 4);
         
         eb1 = new ExpressionBlock(null,3, ExpressTypes.ARITH, "+");
         eb2 = new ExpressionBlock(null,4, ExpressTypes.LOGIC, ">=");
         eb3 = new ExpressionBlock(null,5, ExpressTypes.ASSIGN, "=");
         
         eb1.setFirstIn(new RefBlock(6,vb1));
         eb1.setSecondIn(new RefBlock(7,vb2));
         eb2.setFirstIn(new RefBlock(8,vb1));
         eb2.setSecondIn(new RefBlock(9,vb2));
         eb3.setFirstIn(new RefBlock(10,vb1));
         eb3.setSecondIn(new RefBlock(11,eb1));
         
         System.out.println(eb1);
         System.out.println(eb2);
         System.out.println(eb3);
         
      }
      catch(Exception e)
      {
         System.err.println(e.getMessage());
         return;
      }
      
   }

}
